/* wait.c: process_wait and resume operations */

#include <assert.h>

#include "../debug.h"
#define verbose SOS_DEBUG_PROC_WAIT
#include <sys/debug.h>

#include "../process.h"

static sos_pcb *global_waiters;

int process_wait(sos_pcb *pcb, int target_pid) {
    int err;
    sos_pcb *target;

    if (global_waiters == NULL) dprintf(6, "NO GLOBAL WAITERS\n");

    if (target_pid == 0) {
        // Waiting on an arbitrary PID
        LL_PREPEND((global_waiters), pcb);
        // assert(global_waiters->reply_cap != 0);
        return 0;
    }

    err = lookup_pcb(target_pid, &target);
    if (err) {
        // Check the children cache - might be a race
        if (list_member(pcb->children, (SOS_LIST_TYPE) target_pid)) {
            dprintf(6, "process_wait: child has already exited\n");
            // We can return immediately
            seL4_MessageInfo_t reply = seL4_MessageInfo_new(0, 0, 0, 2);
            // Set error code
            seL4_SetMR(0, SOS_PROCESS_SUCCESS);
            // Set PID
            seL4_SetMR(1, (seL4_Word) target_pid);
            // Call back waiter
            dprintf(6, "process_wait: resuming PID %d\n", (pcb->pid));
            seL4_Send((pcb->reply_cap), reply);
            cspace_free_slot(cur_cspace, (pcb->reply_cap));
        } else return err;
    }

    LL_PREPEND((target->waiter), pcb);

    return 0;
}

int process_resume_waiters(sos_pcb *pcb) {
    int err;
    sos_pcb *waiter, *tmp;
    seL4_MessageInfo_t reply;

    LL_FOREACH_SAFE((pcb->waiter), waiter, tmp) {
        // Delete from waiting queue
        LL_DELETE(pcb->waiter, waiter);

        // Process might have been killed while waiting
        if (waiter->abort) {
            free(waiter);
        } else {
            reply = seL4_MessageInfo_new(0, 0, 0, 2);
            // Set error code
            seL4_SetMR(0, SOS_PROCESS_SUCCESS);
            // Set PID
            seL4_SetMR(1, (seL4_Word) pcb->pid);
            // Call back waiter
            dprintf(6, "process_wait: resuming PID %d\n", (waiter->pid));
            seL4_Send((waiter->reply_cap), reply);
            cspace_free_slot(cur_cspace, (waiter->reply_cap));
            // Clear cap and queue fields
            waiter->reply_cap = 0;
            waiter->next = NULL;
        }
    };

    return SOS_PROCESS_SUCCESS;
}

int process_resume_global_waiters(sos_pcb *pcb) {
    int err;
    sos_pcb *waiter, *tmp;
    seL4_MessageInfo_t reply;

    LL_FOREACH_SAFE((global_waiters), waiter, tmp) {
        dprintf(7, "waiter = %p\n", waiter);
        dprintf(7, "waiter pid = %d\n", waiter->pid);
        // Delete from global waiters queue
        LL_DELETE((global_waiters), waiter);
        if (waiter == pcb) {
            // Process being killed shouldn't be woken up!
            dprintf(6, "process_resume_global_waiters: dying proc in queue\n");
            cspace_free_slot(cur_cspace, (waiter->reply_cap));
            (waiter->reply_cap) = 0;
            // waiter->next = NULL;
        } else if (waiter->abort) {
            // Dangling process - clean it up
            free(waiter);
        } else {
            reply = seL4_MessageInfo_new(0, 0, 0, 2);

            // Set error code and PID
            seL4_SetMR(0, SOS_PROCESS_SUCCESS);
            seL4_SetMR(1, (seL4_Word) pcb->pid);

            // Call back waiter
            dprintf(6, "process_wait: resuming PID %d\n", (waiter->pid));
            if (waiter->reply_cap != 0) {
                seL4_Send((waiter->reply_cap), reply);
                cspace_free_slot(cur_cspace, (waiter->reply_cap));
                (waiter->reply_cap) = 0;
            }
        }
        dprintf(7, "finished\n");
    };

    dprintf(6, "process_resume_global_waiters: finished\n");
    return SOS_PROCESS_SUCCESS;
}
